﻿using CacheProvider.AppFabric;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using Microsoft.ApplicationServer.Caching;
using System.Collections.Specialized;
using CacheProvider;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Data;
using System.Threading;
using CacheProviderEntities;

namespace AppFabricCacheProviderUnitTests
{
    [TestClass()]
    
    public partial class AppFabricCacheProviderTest
    {

        private TestContext testContextInstance;

        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        private AppFabricCacheProvider target;

        #region ADDITIONAL TEST ATTRIBUTES
        //// 
        ////You can use the following additional attributes as you write your tests:
        ////
        ////Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{

        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test



        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        [TestInitialize()]
        public void TestInitialize()
        {
            target = new AppFabricCacheProvider();
            var nameValueConfig = new NameValueCollection();
            target.Initialize("AppFabricCacheProvider", nameValueConfig);
            target.Remove("TestKey");
        }

        [TestMethod()]
        public void NameTest()
        {
            string actual;
            actual = target.Name;
            Assert.AreEqual("AppFabricCacheProvider", actual);
        }

        [TestMethod()]
        public void IsRegionSupportedTest()
        {
            bool actual;
            actual = target.IsRegionSupported;
            Assert.IsTrue(actual);
        }

        [TestMethod()]
        public void IsConcurrencySupportedTest()
        {
            bool actual;
            actual = target.IsConcurrencySupported;
            Assert.IsTrue(actual);
        }

        [TestMethod()]
        public void IsCacheTagSupportedTest()
        {
            bool actual;
            actual = target.IsCacheTagSupported;
            Assert.IsTrue(actual);
        }

        [TestMethod()]
        public void DescriptionTest()
        {
            string actual;
            actual = target.Description;
            Assert.AreEqual("Cache Provider which wraps Microsoft.ApplicationServer.Caching(AppFabric)", actual);
        }

        [TestMethod()]
        public void DefaultExpirationTimeInMilliSecondsTest()
        {
            long expected = 60000;
            long actual;
            target.DefaultExpirationTimeInMilliSeconds = expected;
            actual = target.DefaultExpirationTimeInMilliSeconds;
            Assert.AreEqual(expected, actual);
        }

        [TestMethod()]

        public void ResetObjectTimeoutTest()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";

            var initialTimeout = TimeSpan.FromMilliseconds(5 * 1000);
            var stopwatch = new Stopwatch();
            target.Add(key, value, initialTimeout);
            stopwatch.Start();
            TimeSpan newTimeout = TimeSpan.FromMilliseconds(10 * 1000);

            //Act
            target.ResetObjectTimeout(key, newTimeout);

            //Assert
            //wait for the initial timeout to expire
            while (stopwatch.ElapsedMilliseconds < initialTimeout.TotalMilliseconds)
                ;

            //value should still be availabe in the cache
            var valueFromCache = target.Get(key) as string;
            Assert.AreEqual(value, valueFromCache, false);

            //wait for the new timeout to expire
            while (stopwatch.ElapsedMilliseconds < newTimeout.TotalMilliseconds)
                ;

            //Add a little latency
            Thread.Sleep(1000);

            //now, value should not be available
            valueFromCache = target.Get(key) as string;
            Assert.IsNull(valueFromCache);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void ResetObjectTimeoutTest_Region()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            string regionName = "TestRegion";
            var initialTimeout = TimeSpan.FromMilliseconds(5 * 1000);
            target.CreateRegion(regionName);
            var stopwatch = new Stopwatch();
            target.Add(key, value, initialTimeout, regionName);
            stopwatch.Start();
            TimeSpan newTimeout = TimeSpan.FromMilliseconds(10 * 1000);

            //Act
            target.ResetObjectTimeout(key, newTimeout, regionName);

            //Assert
            //wait for the initial timeout to expire
            while (stopwatch.ElapsedMilliseconds < initialTimeout.TotalMilliseconds)
                ;

            //value should still be availabe in the cache
            var valueFromCache = target.Get(key, regionName) as string;
            Assert.AreEqual(value, valueFromCache, false);

            //wait for the new timeout to expire
            while (stopwatch.ElapsedMilliseconds < newTimeout.TotalMilliseconds)
                ;

            //Add a little latency
            Thread.Sleep(1000);

            //now, value should not be available
            valueFromCache = target.Get(key, regionName) as string;
            Assert.IsNull(valueFromCache);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void RemoveRegionTest()
        {
            string regionName = "TestRegion";
            target.CreateRegion(regionName);


            bool actual = target.RemoveRegion(regionName);

            Assert.IsTrue(actual);
        }

        [TestMethod()]
        public void RemoveTest_LockId()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId;
            string value = "Test value";
            target.Add(key, value);
            target.GetAndLock(key, timeout, out lockId);

            //Act
            bool actual = target.Remove(key, lockId);

            //Assert
            Assert.IsTrue(actual);
            Assert.IsNull(target.Get(key));

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]
        public void RemoveTest_Region()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            string regionName = "TestRegion";
            target.CreateRegion(regionName);
            target.Add(key, value, regionName);

            //Act
            bool actual = target.Remove(key, regionName);

            //Assert
            Assert.IsTrue(actual);
            Assert.IsNull(target.Get(key, regionName));

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void RemoveTest_LockId_Region()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId;
            string value = "Test value";
            string regionName = "TestRegion";
            target.RemoveRegion(regionName); //Remove if exists
            target.CreateRegion(regionName);
            target.Add(key, value, regionName);
            target.GetAndLock(key, timeout, out lockId, regionName);

            //Act
            bool actual = target.Remove(key, lockId, regionName);

            //Assert
            Assert.IsTrue(actual);
            Assert.IsNull(target.Get(key, regionName));

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]
        [ExpectedException(typeof(NotImplementedException))]
        public void RemoveAllTest()
        {
            target.RemoveAll();
        }

        [TestMethod()]
        public void RemoveTest()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            bool actual;
            target.Add(key, value);

            //Act            
            actual = target.Remove(key);

            //Assert
            Assert.IsTrue(actual);
            Assert.IsNull(target.Get(key));

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void PutTest_Timeout_CacheTags_Region()
        {
            //Arrange
            TimeSpan timeout = TimeSpan.FromMilliseconds(10 * 1000);
            string key = "TestKey";
            string value = "Test value";
            var cacheTags = new string[] { "Tag1", "Tag2" };
            string regionName = "TestRegion";
            target.CreateRegion(regionName);

            //Act
            bool actual = target.Put(key, value, timeout, cacheTags, regionName);

            //Assert
            Assert.IsTrue(actual);
            Assert.AreEqual(value, target.Get(key, regionName) as string, false);
            var objectByCacheTags = target.GetObjectsByAllTags(cacheTags, regionName).FirstOrDefault();
            Assert.AreEqual(key, objectByCacheTags.Key, false);
            Assert.AreEqual(value, objectByCacheTags.Value as string, false);

            //Wait for cache to expire
            Thread.Sleep(timeout);

            Assert.IsNull(target.Get(key, regionName));

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]
        public void PutTest_CacheTags()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            var cacheTags = new string[] { "Tag1", "Tag2" };

            //Act
            bool actual = target.Put(key, value, cacheTags);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key) as string;
            Assert.AreEqual(value, valueNow, false);
            //Cache Tags are supported only in Regions.  Hence, the following calls won't succeed.
            //var objectByCacheTags = target.GetObjectsByAllTags(cacheTags, regionName).FirstOrDefault();
            //Assert.AreEqual(key, objectByCacheTags.Key, false);
            //Assert.AreEqual(value, objectByCacheTags.Value as string, false);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void PutTest_CacheTags_Region()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            string regionName = "TestRegion";
            var cacheTags = new string[] { "Tag1", "Tag2" };
            target.RemoveRegion(regionName);//if it exists
            target.CreateRegion(regionName);

            //Act
            bool actual = target.Put(key, value, cacheTags, regionName);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual(value, valueNow, false);
            var objectByCacheTags = target.GetObjectsByAllTags(cacheTags, regionName).FirstOrDefault();
            Assert.AreEqual(key, objectByCacheTags.Key, false);
            Assert.AreEqual(value, objectByCacheTags.Value as string, false);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void PutTest_Region()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            string regionName = "TestRegion";
            target.RemoveRegion(regionName);//if it exists
            target.CreateRegion(regionName);

            //Act
            bool actual = target.Put(key, value, regionName);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual(value, valueNow, false);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void PutTest_Timeout_Region()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(10 * 1000);
            string value = "Test value";
            string regionName = "TestRegion";
            target.RemoveRegion(regionName);//if it exists
            target.CreateRegion(regionName);

            //Act
            bool actual = target.Put(key, value, timeout, regionName);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual(value, valueNow, false);

            //wait for the cache entry to expire
            Thread.Sleep(timeout);

            valueNow = target.Get(key, regionName) as string;
            Assert.IsNull(valueNow);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void PutTest_Timeout_CacheTags_InUpdateMode()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(10 * 1000);
            string value = "Test value";
            var cacheTags = new string[] { "Tag1", "Tag2" };
            target.Add(key, value, timeout, cacheTags);
            //Act
            bool actual = target.Put(key, value + " Updated", timeout, cacheTags);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);
            //CacheTags are only supported in Regions.  Hence, the following statements won't work.
            //var objectByTag = target.GetObjectsByAllTags(cacheTags, regionName).FirstOrDefault();
            //Assert.AreEqual(key, objectByTag.Key, false);
            //Assert.AreEqual(value + " Updated", objectByTag.Value as string, false);

            //wait for the cache entry to expire
            Thread.Sleep(timeout);

            valueNow = target.Get(key) as string;
            Assert.IsNull(valueNow);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void PutTest_Timeout_CacheTags_InAddMode()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);
            string value = "Test value";
            var cacheTags = new string[] { "Tag1", "Tag2" };

            //Act
            bool actual = target.Put(key, value, timeout, cacheTags);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key) as string;
            Assert.AreEqual(value, valueNow, false);
            //CacheTags are only supported in Regions.  Hence, the following statements won't work.
            //var objectByTag = target.GetObjectsByAllTags(cacheTags, regionName).FirstOrDefault();
            //Assert.AreEqual(key, objectByTag.Key, false);
            //Assert.AreEqual(value + " Updated", objectByTag.Value as string, false);

            //wait for the cache entry to expire
            Thread.Sleep(timeout);

            valueNow = target.Get(key) as string;
            Assert.IsNull(valueNow);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void PutAndUnlockTest_Region()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId;
            string value = "Test value";
            string regionName = "TestRegion";
            target.RemoveRegion(regionName); //Remove if exists
            target.CreateRegion(regionName);
            target.Add(key, value, regionName);
            target.GetAndLock(key, timeout, out lockId, regionName);


            //Act
            bool actual = target.PutAndUnlock(key, value + " Updated", lockId, regionName);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void PutAndUnlockTest_Timout_CacheTags()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId;
            string value = "Test value";
            var cacheTags = new string[] { "Tag1", "Tag2" };
            target.Add(key, value);
            target.GetAndLock(key, timeout, out lockId);


            //Act
            bool actual = target.PutAndUnlock(key, value + " Updated", lockId, timeout, cacheTags);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);
            //CacheTags are supported only in Regions.  Hence, the GetObjectsByXXXTag() methods won't work here.
            //var objectByTag = target.GetObjectsByAllTags(cacheTags, regionName).FirstOrDefault();
            //Assert.AreEqual(key, objectByTag.Key, false);
            //Assert.AreEqual(value + " Updated", objectByTag.Value as string, false);

            //wait for the cache entry to expire
            Thread.Sleep(timeout);

            valueNow = target.Get<string>(key);
            Assert.IsNull(valueNow);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void PutAndUnlockTest_Timeout()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId;
            string value = "Test value";
            target.Add(key, value);
            target.GetAndLock(key, timeout, out lockId);


            //Act
            bool actual = target.PutAndUnlock(key, value + " Updated", lockId, timeout);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);

            //wait for the cache entry to expire
            Thread.Sleep(timeout);

            valueNow = target.Get(key) as string;
            Assert.IsNull(valueNow);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void PutAndUnlockTest_CacheTags_Region()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId;
            string value = "Test value";
            string regionName = "TestRegion";
            var cacheTags = new string[] { "Tag1", "Tag2" };
            target.RemoveRegion(regionName); //Remove if exists
            target.CreateRegion(regionName);
            target.Add(key, value, regionName);
            target.GetAndLock(key, timeout, out lockId, regionName);


            //Act
            bool actual = target.PutAndUnlock(key, value + " Updated", lockId, cacheTags, regionName);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);
            var objectByTag = target.GetObjectsByAllTags(cacheTags, regionName).FirstOrDefault();
            Assert.AreEqual(key, objectByTag.Key, false);
            Assert.AreEqual(value + " Updated", objectByTag.Value as string, false);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void PutAndUnlockTest_Timeout_Region()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId;
            string value = "Test value";
            string regionName = "TestRegion";
            target.RemoveRegion(regionName); //Remove if exists
            target.CreateRegion(regionName);
            target.Add(key, value, regionName);
            target.GetAndLock(key, timeout, out lockId, regionName);


            //Act
            bool actual = target.PutAndUnlock(key, value + " Updated", lockId, timeout, regionName);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);

            //wait for the cache entry to expire
            Thread.Sleep(timeout);

            valueNow = target.Get(key, regionName) as string;
            Assert.IsNull(valueNow);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void PutAndUnlockTest_Timeout_CacheTags_Region()
        {

            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId;
            string value = "Test value";
            string regionName = "TestRegion";
            var cacheTags = new string[] { "Tag1", "Tag2" };
            target.RemoveRegion(regionName); //Remove if exists
            target.CreateRegion(regionName);
            target.Add(key, value, regionName);
            target.GetAndLock(key, timeout, out lockId, regionName);


            //Act
            bool actual = target.PutAndUnlock(key, value + " Updated", lockId, timeout, cacheTags, regionName);

            //Assert     
            Assert.IsTrue(actual);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);
            var objectByTag = target.GetObjectsByAllTags(cacheTags, regionName).FirstOrDefault();
            Assert.AreEqual(key, objectByTag.Key, false);
            Assert.AreEqual(value + " Updated", objectByTag.Value as string, false);

            //wait for the cache entry to expire
            Thread.Sleep(timeout);

            valueNow = target.Get(key, regionName) as string;
            Assert.IsNull(valueNow);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void PutAndUnlockTest_Simple()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);//Locks the key for this timespan
            Guid lockId = new Guid();
            string value = "Test value";
            bool actual;
            bool expected = true;
            //Remove if exists
            target.Remove(key);
            target.Add(key, value);
            string valueFromCache = target.GetAndLock(key, timeout, out lockId) as string;


            //Act
            //update the value from cache
            valueFromCache = valueFromCache + " updated";
            actual = target.PutAndUnlock(key, valueFromCache, lockId);

            //Assert
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(valueFromCache, target.Get<string>(key), false);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]
        public void PutTest_SQLDependency_Timeout_TestingTimeout()
        {
            //Arrange
            string key = "TestKey";
            bool expected = true;
            TimeSpan timeout = TimeSpan.FromMilliseconds(15 * 1000);
            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBConnectionString = GetConnectionString(),
                DBName = "AdventureWorks",
                IsSQLSelectQueryBased = true,
                IsSQLTableBased = true
            };
            dependencyInfo.SelectQueries.Add(GetSQL());
            dependencyInfo.SQLTables.Add("sales.Currency");
            var value = GetProducts();
            var stopwatch = new Stopwatch();

            //Act
            bool actual = target.Put(key, value, dependencyInfo, timeout);
            stopwatch.Start();

            //Assert
            Assert.AreEqual(expected, actual);
            var retrievedValue = target.Get(key) as List<Product>;
            CollectionAssert.AreEqual(value, retrievedValue);

            //wait for the cache to time-out
            Thread.Sleep(timeout);

            retrievedValue = target.Get(key) as List<Product>;

            Assert.IsNull(retrievedValue);
        }

        [TestMethod()]
        public void PutTest_SQLDependency_Timeout_TestingDependency()
        {
            //Arrange
            string key = "TestKey";
            bool expected = true;
            var timeout = TimeSpan.FromMilliseconds(15 * 1000);
            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBConnectionString = GetConnectionString(),
                DBName = "AdventureWorks",
                IsSQLSelectQueryBased = true
            };
            dependencyInfo.SelectQueries.Add(GetSQL());

            List<Product> value = GetProducts();
            bool actual;
            var stopwatch = new Stopwatch();

            //Act
            actual = target.Put(key, value, dependencyInfo, timeout);
            stopwatch.Start();

            //Assert
            Assert.AreEqual(expected, actual);
            var retrievedValue = target.Get(key) as List<Product>;
            CollectionAssert.AreEqual(value, retrievedValue);

            //wait for the cache to time-out
            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;



            //Make some related changes to check dependency
            UpdateProduct();
            Thread.Sleep(5000); //wait for a few seconds
            retrievedValue = target.Get(key) as List<Product>;
            if (retrievedValue == null)
            {
                var newProductList = GetProducts();
                if (target.Put(key, newProductList, dependencyInfo, timeout))
                {
                    retrievedValue = target.Get(key) as List<Product>;
                    CollectionAssert.AreEqual(newProductList, retrievedValue);
                }
            }
            else
            {
                Assert.Fail("Cache is not updating even after 5 seconds latency");
            }

            //Clean-up
            target.Remove(key);


        }

        [TestMethod()]
        public void PutTest_Simple()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test Value";
            bool expected = true;
            bool actual;

            //Act
            actual = target.Put(key, value);

            //Assert
            Assert.AreEqual(expected, actual);
            string valueRetrieved = target.Get<string>(key);
            Assert.AreEqual(value, valueRetrieved, false);

            //Clean-up
            target.Remove(key);
        }

        [TestMethod()]
        public void PutTest_Timeout()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test Value";
            TimeSpan timeout = TimeSpan.FromMilliseconds(10 * 1000);
            bool expected = true;
            bool actual;
            Stopwatch stopwatch = new Stopwatch();

            //Act
            actual = target.Put(key, value, timeout);
            stopwatch.Start();

            //Assert
            Assert.AreEqual(expected, actual);
            string valueRetrieved = target.Get<string>(key);
            Assert.AreEqual(value, valueRetrieved, false);

            //wait for the cache to time-out
            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;

            valueRetrieved = target.Get<string>(key);
            Assert.IsNull(valueRetrieved);

            //Clean-up
            target.Remove(key);

        }

        [TestMethod()]
        public void PutTest_SqlDependency()
        {

            string key = "TestKey";
            PutWithSqlDependency(key);
        }

        private void PutWithSqlDependency(string key)
        {
            //Arrange
            bool expected = true;
            DependencyInfo dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBConnectionString = GetConnectionString(),
                DBName = "AdventureWorks",
                IsSQLSelectQueryBased = true
            };

            dependencyInfo.SelectQueries.Add(GetSQL());

            List<Product> value = GetProducts();
            bool actual;

            //Act
            actual = target.Put(key, value, dependencyInfo);

            //Assert
            Assert.AreEqual(expected, actual);
            var retrievedValue = target.Get(key) as List<Product>;
            CollectionAssert.AreEqual(value, retrievedValue);


            //Make some related changes to check dependency
            UpdateProduct();
            Thread.Sleep(1000); //wait a second
            retrievedValue = target.Get(key) as List<Product>;
            if (retrievedValue == null)
            {
                var newProductList = GetProducts();
                if (target.Put(key, newProductList, dependencyInfo))
                {
                    retrievedValue = target.Get(key) as List<Product>;
                    CollectionAssert.AreEqual(newProductList, retrievedValue);
                }
            }
            else
            {
                Assert.Fail("Cache is not invalidated even after 1 second latency");
            }

            //Clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void GetSystemRegionsTest()
        {
            List<string> actual;
            actual = target.GetSystemRegions().ToList();
            Assert.IsTrue(actual.Count > 0);
        }

        [TestMethod()]

        public void GetSystemRegionNameTest()
        {
            string regionName = "TestRegion";
            try
            {
                target.CreateRegion(regionName);
                target.Add("TestKey 1", "Test Value 1", regionName);
                target.Add("TestKey 6", "Test Value 6");

                var actual = target.GetSystemRegionName("TestKey 1");
                Assert.IsNotNull(actual);

                actual = target.GetSystemRegionName("TestKey 6");
                Assert.IsNotNull(actual);
            }
            finally
            {
                //Clean-up
                target.Remove("TestKey 6");
                target.RemoveRegion(regionName);
            }
        }

        [TestMethod()]

        public void GetObjectsInRegionImplTest()
        {
            //Arrange
            IEnumerable<string> cacheTags = new string[] { "Tag 1", "Tag 2" };
            string regionName = "TestRegion";
            target.CreateRegion(regionName);
            target.Add("TestKey 1", "Test Value 1", cacheTags, regionName);
            target.Add("TestKey 2", "Test Value 2", cacheTags, regionName);
            target.Add("TestKey 3", "Test Value 3", new string[] { "Tag 1" }, regionName);
            target.Add("TestKey 4", "Test Value 4", new string[] { "Tag 2" });
            target.Add("TestKey 5", "Test Value 5", regionName);
            target.Add("TestKey 6", "Test Value 6");
            target.Add("TestKey 7", "Test Value 7");

            //Act
            var actualObjectsInRegion = target.GetObjectsInRegion(regionName);

            //Assert
            var actualObjectsInRegionAsList = actualObjectsInRegion.ToList();
            Assert.IsTrue(actualObjectsInRegionAsList.Count == 4);
            foreach (var item in actualObjectsInRegionAsList)
            {
                if (item.Key == "TestKey 1")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 1"));
                else if (item.Key == "TestKey 2")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 2"));
                else if (item.Key == "TestKey 3")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 3"));
                else if (item.Key == "TestKey 5")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 5"));
                else
                    Assert.Fail("Unexpected Key {0} found", item.Key);
            }

            //Clean-up;
            target.RemoveRegion(regionName);
            target.Remove("TestKey 6");
            target.Remove("TestKey 7");
        }

        [TestMethod()]

        public void GetObjectsByTagTest_Region()
        {
            //Arrange
            IEnumerable<string> cacheTags = new string[] { "Tag 1", "Tag 2" };
            string regionName = "TestRegion";
            target.CreateRegion(regionName);
            target.Add("TestKey 1", "Test Value 1", cacheTags, regionName);
            target.Add("TestKey 2", "Test Value 2", cacheTags, regionName);
            target.Add("TestKey 3", "Test Value 3", new string[] { "Tag 1" }, regionName);
            target.Add("TestKey 4", "Test Value 4", new string[] { "Tag 2" }, regionName);
            target.Add("TestKey 5", "Test Value 5", new string[] { "Tag 3" }, regionName);
            target.Add("TestKey 6", "Test Value 6", regionName);

            //Act
            var actual = target.GetObjectsByTag("Tag 1", regionName);

            //Assert
            var actualAsList = actual.ToList();
            Assert.IsTrue(actualAsList.Count == 3);

            foreach (var item in actualAsList)
            {
                if (item.Key == "TestKey 1")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 1"));
                else if (item.Key == "TestKey 2")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 2"));
                else if (item.Key == "TestKey 3")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 3"));
                else
                    Assert.Fail("Unexpected Key {0} found", item.Key);
            }

            //Clean-up;
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void GetObjectsByAnyTagTest_Region()
        {
            //Arrange
            IEnumerable<string> cacheTags = new string[] { "Tag 1", "Tag 2" };
            string regionName = "TestRegion";
            target.CreateRegion(regionName);
            target.Add("TestKey 1", "Test Value 1", cacheTags, regionName);
            target.Add("TestKey 2", "Test Value 2", cacheTags, regionName);
            target.Add("TestKey 3", "Test Value 3", new string[] { "Tag 1" }, regionName);
            target.Add("TestKey 4", "Test Value 4", new string[] { "Tag 2" }, regionName);
            target.Add("TestKey 5", "Test Value 5", new string[] { "Tag 3" }, regionName);
            target.Add("TestKey 6", "Test Value 6", regionName);

            //Act
            var actual = target.GetObjectsByAnyTag(new string[] { "Tag 1", "Tag 3" }, regionName);

            //Assert
            var actualAsList = actual.ToList();
            Assert.IsTrue(actualAsList.Count == 4);
            foreach (var item in actualAsList)
            {
                if (item.Key == "TestKey 1")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 1"));
                else if (item.Key == "TestKey 2")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 2"));
                else if (item.Key == "TestKey 3")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 3"));
                else if (item.Key == "TestKey 5")
                    Assert.IsTrue((item.Value as string).Equals("Test Value 5"));
                else
                    Assert.Fail("Unexpected Key {0} found", item.Key);
            }

            //Clean-up;
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        public void GetObjectsByAllTagsTest_Region()
        {
            //Arrange
            IEnumerable<string> cacheTags = new string[] { "Tag 1", "Tag 2" };
            var threadId = Thread.CurrentThread.ManagedThreadId;
            string regionName = "TestRegion" + threadId;
            target.CreateRegion(regionName);
            target.Add("TestKey 1" + threadId, "Test Value 1", cacheTags, regionName);
            target.Add("TestKey 2" + threadId, "Test Value 2", cacheTags, regionName);
            target.Add("TestKey 3" + threadId, "Test Value 3", new string[] { "Tag 1" }, regionName);
            target.Add("TestKey 4" + threadId, "Test Value 4", new string[] { "Tag 2" }, regionName);
            target.Add("TestKey 5" + threadId, "Test Value 5", regionName);

            //Act
            var actual = target.GetObjectsByAllTags(cacheTags, regionName);

            //Assert
            var actualAsList = actual.ToList();
            Assert.IsTrue(actualAsList.Count == 2);
            foreach (var item in actualAsList)
            {
                if (item.Key == "TestKey 1" + threadId)
                    Assert.IsTrue((item.Value as string).Equals("Test Value 1"));
                else if (item.Key == "TestKey 2" + threadId)
                    Assert.IsTrue((item.Value as string).Equals("Test Value 2"));
                else
                    Assert.Fail("Unexpected Key {0} found", item.Key);
            }

            //Clean-up;
            target.RemoveRegion(regionName);

        }

        [TestMethod()]

        public void GetTest_Region()
        {
            //Arrange
            string key = "TestKey";
            string regionName = "TestRegion";
            string expected = "Test Value";
            target.CreateRegion(regionName);
            target.Add(key, expected, regionName);

            //Act
            string actual = target.Get(key, regionName) as string;

            //Assert
            Assert.AreEqual(expected, actual, false);

            //Clean-up
            target.Remove(key, regionName);
        }

        [TestMethod()]

        public void GetAndLockTest_Timeout_ForceLock()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(60 * 1000);//Locks the key for this timespan
            Guid lockId = new Guid();
            string value = null;
            bool forceLock = true;
            string actual;
            //target.Add(key, value);

            //Act
            //Notice that if forceLock = true, the key-value pair may not exist in the Cache.
            actual = target.GetAndLock(key, timeout, out lockId, forceLock) as string;
            Assert.AreEqual(value, actual, false);

            //Assert
            target.PutAndUnlock(key, "Test Value", lockId);

            string valueNow = target.Get(key) as string;
            Assert.AreEqual("Test Value", valueNow, false);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        [ExpectedException(typeof(DataCacheException),
         "ErrorCode<ERRCA0012>:SubStatus<ES0001>:Object being referred to is not locked by any client.")]
        public void GetAndLockTest_Timeout_LockCheckPostTimeout()
        {
            //Arrange
            string key = "TestKey " + Thread.CurrentThread.ManagedThreadId;
            TimeSpan timeout = TimeSpan.FromMilliseconds(10 * 1000);//Locks the key for this timespan
            Guid lockId = new Guid();
            string value = "Test value";
            string actual;
            target.Add(key, value);
            var stopwatch = new Stopwatch();

            //Act
            actual = target.GetAndLock(key, timeout, out lockId) as string;
            stopwatch.Start();

            Assert.AreEqual(value, actual, false);

            //Waiting for the lock to timeout
            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;

            //Post timeout, PutAndUnlock throws exception as there is no lock.            
            try
            {
                target.PutAndUnlock(key, value + " Updated", lockId);
            }
            finally
            {
                //clean-up
                target.Remove(key);
            }
        }

        [TestMethod()]

        public void GetAndLockTest_Timeout()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(60 * 1000);//Locks the key for this timespan
            Guid lockId = new Guid();
            string value = "Test value";
            string actual;
            target.Add(key, value);

            //Act
            actual = target.GetAndLock(key, timeout, out lockId) as string;
            Assert.AreEqual(value, actual, false);

            //Assert
            target.PutAndUnlock(key, value + " Updated", lockId);
            string valueNow = target.Get(key) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);

            //clean-up
            target.Remove(key);
        }

        [TestMethod()]

        public void GetAndLockTest_Timeout_Region_ForceLock()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(60 * 1000);//Locks the key for this timespan
            Guid lockId = new Guid();
            string value = null;
            string regionName = "TestRegion";
            bool forceLock = true;
            string actual;
            //Remove if exists
            target.RemoveRegion(regionName);
            target.CreateRegion(regionName);
            //target.Add(key, value, regionName);

            //Act
            //Notice that if forceLock = true, the key-value pair may not exist in the Cache.
            actual = target.GetAndLock(key, timeout, out lockId, regionName, forceLock) as string;
            Assert.AreEqual(value, actual, false);

            //Assert
            target.PutAndUnlock(key, "Test Value", lockId, regionName);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual("Test Value", valueNow, false);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]

        [ExpectedException(typeof(DataCacheException),
        "ErrorCode<ERRCA0011>:SubStatus<ES0001>:Object being referred to is currently locked, and cannot be accessed until it is unlocked by the locking client. Please retry later.")]
        public void GetAndLockTest_Timeout_Region_NegativeCase()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(60 * 1000);//Locks the key for this timespan
            Guid lockId = new Guid();
            string value = "Test value";
            string regionName = "TestRegion";
            string actual;
            //Remove if exists
            target.RemoveRegion(regionName);
            target.CreateRegion(regionName);
            target.Add(key, value, regionName);

            //Act
            actual = target.GetAndLock(key, timeout, out lockId, regionName) as string;
            //Assert
            Assert.AreEqual(value, actual, false);

            try
            {
                //This call should throw exception as the key is already locked.
                actual = target.GetAndLock(key, timeout, out lockId, regionName) as string;
            }
            finally
            {
                //clean-up
                target.RemoveRegion(regionName);
            }
        }

        [TestMethod()]

        public void GetAndLockTest_Timeout_Region()
        {
            //Arrange
            string key = "TestKey";
            TimeSpan timeout = TimeSpan.FromMilliseconds(60 * 1000);//Locks the key for this timespan
            Guid lockId = new Guid();
            string value = "Test value";
            string regionName = "TestRegion";
            string actual;
            //Remove if exists
            target.RemoveRegion(regionName);
            target.CreateRegion(regionName);
            target.Add(key, value, regionName);

            //Act
            actual = target.GetAndLock(key, timeout, out lockId, regionName) as string;
            Assert.AreEqual(value, actual, false);

            //Assert
            target.PutAndUnlock(key, value + " Updated", lockId, regionName);
            string valueNow = target.Get(key, regionName) as string;
            Assert.AreEqual(value + " Updated", valueNow, false);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]
        public void GetTest()
        {
            //Arrange
            string key = "TestKey";
            string expected = "Test Value";
            string actual;
            target.Add(key, expected);

            //Act
            actual = target.Get(key) as string;

            //Assert
            Assert.AreEqual(expected, actual);

            //Clean-up
            target.Remove(key);
        }

        [TestMethod()]
        public void GetTest_Generic()
        {
            //Arrange
            string key = "TestKey " + Thread.CurrentThread.ManagedThreadId;
            string expected = "Test Value";
            string actual;
            target.Add(key, expected);

            //Act
            actual = target.Get<string>(key);

            //Assert
            Assert.AreEqual(expected, actual);

            //Clean-up
            target.Remove(key);
        }

        [TestMethod()]
        public void GetTest_ManyKeys()
        {
            //Arrange
            IDictionary<string, object> keyValues = new Dictionary<string, object>();
            for (int i = 0; i < 10; i++) //Preparing sample data to be cached.
            {
                string key = "TestKey " + i;
                string value = "TestValue " + i;
                keyValues.Add(new KeyValuePair<string, object>(key, value));
                //Remove if already exist
                target.Remove(key);
                bool success = target.Add(key, value);
                if (!success)
                    throw new Exception("Add failed");

            }

            IDictionary<string, object> actual;

            //Act
            actual = target.Get(keyValues.Keys.ToArray());

            //Assert
            Assert.AreEqual(actual.Count, keyValues.Count);
            Assert.IsTrue(actual.All(pair => keyValues.Contains(pair)));

            //Clean-up
            keyValues.All(pair => target.Remove(pair.Key));
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentException),
        "Region names should contain only alphanumeric characters.")]
        public void CreateRegionTest_RegionNameAsEmptyString()
        {
            string regionName = string.Empty;
            bool actual;
            actual = target.CreateRegion(regionName);
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentException),
        "Region names should contain only alphanumeric characters.")]
        public void CreateRegionTest_RegionNameContainingNonAlphaNumericChars()
        {
            //Notice the @ character in the region name.
            string regionName = "TestRegion@";
            bool actual;
            actual = target.CreateRegion(regionName);
        }

        [TestMethod()]
        public void CreateRegionTest_PositiveCase()
        {
            string regionName = "TestRegion";
            bool expected = true;
            bool actual;
            actual = target.CreateRegion(regionName);
            Assert.AreEqual(expected, actual);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]
        public void CreateRegionTest_AlreadyExistingRegion()
        {
            //Arrange
            string regionName = "TestRegion";
            bool expected = false; //If a region already exist, CreateRegion() returns false.
            bool actual;
            target.CreateRegion(regionName);//Creating region to produce already existing region scenario 

            //Act
            actual = target.CreateRegion(regionName);

            //Assert
            Assert.AreEqual(expected, actual);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]
        public void ClearRegionTest()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            string regionName = "TestRegion";
            bool expected = true;
            bool actual;
            //Create region if it does not exist.
            target.CreateRegion(regionName);
            actual = target.Add(key, value, regionName);

            //Assert state before ClearRegion
            Assert.AreEqual(expected, actual);
            string retrievedValue = target.Get(key, regionName) as string;
            Assert.AreEqual(value, retrievedValue, false);


            actual = target.ClearRegion(regionName);

            //Assert State after ClearRegion
            Assert.AreEqual(expected, actual);
            retrievedValue = target.Get(key, regionName) as string;
            Assert.AreNotEqual(value, retrievedValue, false);
            Assert.IsNull(retrievedValue);
        }

        [TestMethod()]
        public void AddTest_Region_RegionExists()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            string regionName = "TestRegion";
            bool expected = true;
            bool actual;
            //Create region if it does not exist.
            target.CreateRegion(regionName);

            //Act
            actual = target.Add(key, value, regionName);

            //Assert
            Assert.AreEqual(expected, actual);
            string retrievedValue = target.Get(key, regionName) as string;
            Assert.AreEqual(value, retrievedValue, false);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]
        [ExpectedException(typeof(DataCacheException),
        "ErrorCode<ERRCA0005>:SubStatus<ES0001>:Region referred to does not exist. Use CreateRegion API to fix the error.")]
        public void AddTest_Region_RegionDoesNotExist()
        {
            string key = "TestKey";
            string value = "Test value";
            string regionName = "TestRegion";
            bool actual;

            //This call fails as region does not exist.
            actual = target.Add(key, value, regionName);
        }

        [TestMethod()]
        public void AddTest_CacheTags_Region_RegionExists()
        {
            //Arrange
            string key = "TestKey";
            string value = "Test value";
            IEnumerable<string> cacheTags = new List<string>() { "Tag1", "Tag2" };
            string regionName = "TestRegion";
            bool expected = true;
            bool actual;
            //Create region if it does not exist.
            target.CreateRegion(regionName);

            //Act
            actual = target.Add(key, value, cacheTags, regionName);

            //Assert(s)
            Assert.AreEqual(expected, actual);
            string valueRetrieved = target.Get(key, regionName) as string;
            Assert.AreEqual(value, valueRetrieved, false);

            var objectsRetrivedByTags = target.GetObjectsByAllTags(cacheTags, regionName);

            int counter = 0;
            //Only one entry should be there in objectsByTag
            foreach (var item in objectsRetrivedByTags)
            {
                counter++;
                Assert.AreEqual(item.Key, key);
                Assert.AreEqual(item.Value, value);
            }

            Assert.AreEqual(1, counter);

            //Clean-up
            target.RemoveRegion(regionName);

        }

        [TestMethod()]
        [ExpectedException(typeof(DataCacheException),
        "ErrorCode<ERRCA0005>:SubStatus<ES0001>:Region referred to does not exist. Use CreateRegion API to fix the error.")]
        public void AddTest_CacheTags_Region_RegionDoesNotExist()
        {
            string key = "TestKey";
            string value = "Test value";
            IEnumerable<string> cacheTags = new List<string>() { "Tag1", "Tag2" };
            string regionName = "TestRegion";
            bool actual;
            //This throws exception as the region does not exist.
            actual = target.Add(key, value, cacheTags, regionName);
        }

        [TestMethod()]
        public void AddTest_CacheTags()
        {
            string key = "TestKey";
            string value = "Test value";
            IEnumerable<string> cacheTags = new List<string>() { "Tag1", "Tag2" };
            bool expected = true;
            bool actual;

            actual = target.Add(key, value, cacheTags);

            Assert.AreEqual(expected, actual);
            string actualValue = target.Get(key) as string;
            Assert.IsTrue(value.Equals(actualValue));

            //**************** IMPORTANT POINT**************************************
            //Cache Tags are supported only for objects that are stored in a region.  
            //Hence, the following call GetObjectsByAllTags() will fail for an object
            //stored without a region but with Cache Tags.            

            //var objectsByTag = target.GetObjectsByAllTags(cacheTags, regionName);

            ////Only one entry should be there in objectsByTag
            //foreach (var item in objectsByTag)
            //{
            //    Assert.AreEqual(item.Key, key);
            //    Assert.AreEqual(item.Value, value);
            //}

            //**********************************************************************
        }

        [TestMethod()]
        public void AddTest_Timeout_Region_RegionExists()
        {
            string key = "TestKey";
            string value = "Test value";
            TimeSpan timeout = TimeSpan.FromMilliseconds(20 * 1000);
            string regionName = "TestRegion";
            bool expected = true;
            bool actual = false;
            var stopwatch = new Stopwatch();
            //Creating a region
            target.CreateRegion(regionName);

            actual = target.Add(key, value, timeout, regionName);

            stopwatch.Start();

            Assert.AreEqual(expected, actual);

            string actualValue = target.Get(key, regionName) as string;
            Assert.IsTrue(value.Equals(actualValue));

            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;

            stopwatch.Stop();

            //The cache entry must have expired by now. 
            object valueNow = target.Get(key, regionName);
            Assert.IsNull(valueNow);

            //clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]
        [ExpectedException(typeof(DataCacheException),
        "ErrorCode<ERRCA0005>:SubStatus<ES0001>:Region referred to does not exist. Use CreateRegion API to fix the error.")]
        public void AddTest_Timeout_Region_RegionDoesNotExist()
        {
            string key = "TestKey";
            string value = "Test value";
            TimeSpan timeout = TimeSpan.FromMilliseconds(20 * 1000);
            string regionName = "TestRegion";
            bool expected = true;
            bool actual = false;
            var stopwatch = new Stopwatch();

            //Trying to add an entry to a region where the region does not exist.
            actual = target.Add(key, value, timeout, regionName);

            //Rest of the code won't execute
            stopwatch.Start();

            Assert.AreEqual(expected, actual);

            string actualValue = target.Get<String>(key);
            Assert.IsTrue(value.Equals(actualValue));

            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;

            stopwatch.Stop();

            //The cache entry must have expired by now. 
            object valueNow = target.Get(key);
            Assert.IsNull(valueNow);

        }

        [TestMethod()]
        public void AddTest_Timeout_CacheTags_NoRegion()
        {
            string key = "TestKey";
            string value = "Test value";
            TimeSpan timeout = TimeSpan.FromMilliseconds(20 * 1000);
            IEnumerable<string> cacheTags = new List<string>() { "Tag1", "Tag2" };
            bool expected = true;
            bool actual;
            var stopwatch = new Stopwatch();

            //wait for cache to expire
            actual = target.Add(key, value, timeout, cacheTags);

            stopwatch.Start();
            Assert.AreEqual(expected, actual);

            string actualValue = target.Get(key) as string;
            Assert.IsTrue(value.Equals(actualValue));

            //**************** IMPORTANT POINT**************************************
            //Cache Tags are supported only for objects that are stored in a region.  
            //Hence, the following call GetObjectsByAllTags() will fail for an object
            //stored without a region but with Cache Tags.            

            //var objectsByTag = target.GetObjectsByAllTags(cacheTags, regionName);

            ////Only one entry should be there in objectsByTag
            //foreach (var item in objectsByTag)
            //{
            //    Assert.AreEqual(item.Key, key);
            //    Assert.AreEqual(item.Value, value);
            //}

            //**********************************************************************

            //wait for cache to expire
            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;

            stopwatch.Stop();

            //The cache entry must have expired by now. 
            object valueNow = target.Get(key);
            Assert.IsNull(valueNow);
        }

        [TestMethod()]
        public void AddTest_Timeout_CacheTags_Region()
        {
            string key = "TestKey";
            string value = "Test value";
            string regionName = "TestRegion";
            TimeSpan timeout = TimeSpan.FromMilliseconds(20 * 1000);
            IEnumerable<string> cacheTags = new List<string>() { "Tag1", "Tag2" };
            bool expected = true;
            bool actual;
            var stopwatch = new Stopwatch();

            target.Remove(key, regionName);
            //Creating region, if it doesnot exist
            target.CreateRegion(regionName);

            actual = target.Add(key, value, timeout, cacheTags, regionName);

            stopwatch.Start();
            Assert.AreEqual(expected, actual);

            string actualValue = target.Get(key, regionName) as string;
            Assert.IsTrue(value.Equals(actualValue));

            var objectsByTag = target.GetObjectsByAllTags(cacheTags, regionName);

            int counter = 0;
            //Only one entry should be there in objectsByTag
            foreach (var item in objectsByTag)
            {
                counter++;
                Assert.AreEqual(item.Key, key);
                Assert.AreEqual(item.Value, value);
            }

            Assert.AreEqual(1, counter);

            //wait for cache to expire
            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;

            stopwatch.Stop();

            //The cache entry must have expired by now. 
            object valueNow = target.Get(key, regionName);
            Assert.IsNull(valueNow);

            //Clean-up
            target.RemoveRegion(regionName);
        }

        [TestMethod()]
        public void AddTest_TimeoutAsTimeSpan()
        {
            string key = "TestKey";
            string value = "Test value";
            TimeSpan timeout = TimeSpan.FromMilliseconds(10 * 1000);
            bool expected = true;
            bool actual;
            var stopwatch = new Stopwatch();
            actual = target.Add(key, value, timeout);
            stopwatch.Start();
            Assert.AreEqual(expected, actual);


            string actualValue = target.Get(key) as string;
            Assert.IsTrue(value.Equals(actualValue));

            //wait for cache to expire
            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;

            stopwatch.Stop();
            //The cache entry must have expired by now. 
            object valueNow = target.Get(key);
            Assert.IsNull(valueNow);
        }

        [TestMethod()]
        public void AddTest_Simple()
        {
            string key = "TestKey";
            string value = "Test value";
            bool expected = true;
            bool actual;

            actual = target.Add(key, value);
            Assert.AreEqual(expected, actual);
            string valueFromCache = target.Get<String>(key);
            Assert.IsTrue(valueFromCache.Equals(value));
        }

        [TestMethod()]
        [ExpectedException(typeof(DataCacheException))]
        public void AddTest_Simple_ExistingKeyThrowsException()
        {
            string key = "TestKey";
            string value = "Test value";
            bool expected = true;
            bool actual;
            //Remove the existing entry (in case, it exists)
            target.Remove(key);

            actual = target.Add(key, value);
            Assert.AreEqual(expected, actual);
            string valueFromCache = target.Get<String>(key);
            Assert.IsTrue(valueFromCache.Equals(value));

            //Trying to add again with the same key, should cause exception
            target.Add(key, value);
        }

        [TestMethod()]
        public void AddTest_ExplicitExpiration()
        {
            string key = "TestKey";
            string value = "Test value";
            long ttlInMilliSeconds = 10 * 1000;
            bool expected = true;
            bool actual;
            var stopwatch = new Stopwatch();
            actual = target.Add(key, value, ttlInMilliSeconds);
            stopwatch.Start();

            Assert.AreEqual(expected, actual);

            string actualValue = target.Get(key) as string;
            Assert.IsTrue(value.Equals(actualValue));

            //wait for cache to expire
            while (stopwatch.ElapsedMilliseconds < ttlInMilliSeconds)
                ;

            //The cache entry must have expired by now. 
            object valueNow = target.Get(key);
            Assert.IsNull(valueNow);
        }

        [TestMethod()]
        public void AddTest_DefaultExpiration()
        {
            string key = "TestKey";
            object value = "Test value";
            bool UseDefaultExpiration = true;
            bool expected = true;
            bool actual;

            actual = target.Add(key, value, UseDefaultExpiration);
            Assert.AreEqual(expected, actual);

            //Clean-up
            target.Remove(key);
        }

        [TestMethod]
        [ExpectedException(typeof(DataCacheException))]
        public void AddTest_DefaultExpiration_ThrowsExceptionForExistingKey()
        {
            string key = "TestKey";
            object value = "Test value";
            bool UseDefaultExpiration = true;
            bool expected = true;
            bool actual;

            actual = target.Add(key, value, UseDefaultExpiration);
            Assert.AreEqual(expected, actual);

            try
            {
                //Trying again to add with the same key.
                target.Add(key, value, UseDefaultExpiration);
            }
            finally
            {
                //Clean-up
                target.Remove(key);
            }
        }

        [TestMethod]
        public void NamedCacheTest_Simple()
        {
            string key = "TestKey";
            object value = "Test value";

            //This named cache must be created first using Powershell New-Cache command
            target.SetNamedCache("TestNamedCache");
            target.Add(key, value);

            var actual = target.Get<string>(key);

            Assert.AreEqual(value, actual);

            target.Remove(key);
        }


        [TestMethod]
        //[ExpectedException(typeof(DataCacheException),
        //    "ErrorCode<ERRCA0009>:SubStatus<ES0001>:Cache referred to does not exist. Contact administrator or use the Cache administration tool to create a Cache")]
        public void NamedCacheTest_DoesnotExist()
        {
            string key = "TestKey";
            object value = "Test value";
            try
            {
                target.SetNamedCache("NonExistingNamedCache");
                var actual = target.Add(key, value);
                Assert.IsFalse(actual);
            }
            finally
            {
                target.SetDefaultCache();
            }

            
        }

        [TestMethod()]
        public void PutTest_WithMultipleSQLQueryDependencies()
        {
            //Arrange
            bool expected = true;
            string key = "TestKey";

            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBConnectionString = GetConnectionString(),
                DBName = "AdventureWorks",
                IsSQLSelectQueryBased = true
            };

            dependencyInfo.SelectQueries.Add("SELECT Name FROM Production.Location");

            dependencyInfo.SelectQueries.Add("SELECT Name FROM Production.Product");

            var value = GetProducts();
            bool actual;

            //Act
            actual = target.Put(key, value, dependencyInfo);

            //Assert
            Assert.AreEqual(expected, actual);
            var retrievedValue = target.Get(key) as List<Product>;
            CollectionAssert.AreEqual(value, retrievedValue);


            //Make some related changes to check dependency
            UpdateProduct();
            Thread.Sleep(1000); //wait a second
            retrievedValue = target.Get(key) as List<Product>;
            if (retrievedValue == null)
            {
                var newProductList = GetProducts();
                if (target.Put(key, newProductList, dependencyInfo))
                {
                    retrievedValue = target.Get(key) as List<Product>;
                    CollectionAssert.AreEqual(newProductList, retrievedValue);
                }
            }
            else
            {
                Assert.Fail("Cache is not invalidated even after 1 second latency");
            }

            //Clean-up
            target.Remove(key);
        }


        [TestMethod()]
        public void PutTest_WithMultipleTableDependencies()
        {
            //Arrange
            bool expected = true;
            string key = "TestKey";

            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBConnectionString = GetConnectionString(),
                DBName = "AdventureWorks",
                IsSQLSelectQueryBased = false,
                IsSQLTableBased = true
            };

            dependencyInfo.SQLTables.Add("Production.Location");
            dependencyInfo.SQLTables.Add("Production.Product");


            var value = GetProducts();
            bool actual;

            //Act
            actual = target.Put(key, value, dependencyInfo);

            //Assert
            Assert.AreEqual(expected, actual);
            var retrievedValue = target.Get(key) as List<Product>;
            CollectionAssert.AreEqual(value, retrievedValue);


            //Make some related changes to check dependency
            UpdateProduct();
            Thread.Sleep(1000); //wait a second
            retrievedValue = target.Get(key) as List<Product>;
            if (retrievedValue == null)
            {
                var newProductList = GetProducts();
                if (target.Put(key, newProductList, dependencyInfo))
                {
                    retrievedValue = target.Get(key) as List<Product>;
                    CollectionAssert.AreEqual(newProductList, retrievedValue);
                }
            }
            else
            {
                Assert.Fail("Cache is not invalidated even after 1 second latency");
            }

            //Clean-up
            target.Remove(key);
        }

        [TestMethod()]
        public void PutTest_Performance()
        {
            
            string key = "TestKey ";
            Int32 limit = 1000;
            string[] keys = new string[limit];
            for (int i = 0; i < limit; i++)
            {
                keys[i] = key + (i + 1);
            }

            var value = GetProducts();
            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBConnectionString = GetConnectionString(),
                DBName = "AdventureWorks",
                IsSQLSelectQueryBased = false,
                IsSQLTableBased = true
            };

            dependencyInfo.SQLTables.Add("Production.Location");
            dependencyInfo.SQLTables.Add("Production.Product");            

            Int32 counter = 0;

            Stopwatch sw = new Stopwatch();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            sw.Start();

            while (counter < limit)
            {
               var actual = target.Put(keys[counter], value, dependencyInfo);

                //Assert
                //Assert.IsTrue(actual);
                counter++;
            }

            sw.Stop();
           
            
        }

        #region H E L P E R   M E T H O D S

        private List<Product> GetProducts()
        {
            List<Product> products = new List<Product>();
            using (var connection = new SqlConnection(GetConnectionString()))
            using (var sqlCommand = new SqlCommand(GetSQL(), connection))
            {
                connection.Open();
                IDataReader reader = sqlCommand.ExecuteReader();
                int productIdIndex = reader.GetOrdinal("ProductId");
                int productNameIndex = reader.GetOrdinal("ProductName");
                int productLocationIndex = reader.GetOrdinal("Location");
                int productQuantityIndex = reader.GetOrdinal("Quantity");
                while (reader.Read())
                {
                    var product = new Product()
                    {
                        ProductId = reader.GetInt32(productIdIndex),
                        Name = reader.GetString(productNameIndex),
                        Location = reader.GetString(productLocationIndex),
                        Quantity = reader.GetInt16(productQuantityIndex)
                    };
                    products.Add(product);
                }
            }

            return products;
        }

        private void UpdateProduct()
        {
            string sql = "update Production.Product" +
                         " set Name='Half-Finger Gloves, M Updated'" +
                         " where ProductID=859";
            using (var connection = new SqlConnection(GetConnectionString()))
            using (var sqlCommand = new SqlCommand(sql, connection))
            {
                connection.Open();
                sqlCommand.ExecuteNonQuery();
            }
        }

        private static string GetConnectionString()
        {
            if (Environment.MachineName.Equals("IN8202039D"))
                return @"Data Source=SRVBLRCAF01\MSBLRTCTD01;Integrated Security=true;" +
                  "Initial Catalog=AdventureWorks;";

            return @"Data Source=RAHUL-PC\SQL2008EXPRESS;Integrated Security=true;" +
              "Initial Catalog=AdventureWorks;";
        }

        private static string GetSQL()
        {
            return "SELECT Production.Product.ProductID AS ProductId, " +
            "Production.Product.Name AS ProductName, " +
            "Production.Location.Name AS Location, " +
            "Production.ProductInventory.Quantity AS Quantity " +
            "FROM Production.Product INNER JOIN " +
            "Production.ProductInventory " +
            "ON Production.Product.ProductID = " +
            "Production.ProductInventory.ProductID " +
            "INNER JOIN Production.Location " +
            "ON Production.ProductInventory.LocationID = " +
            "Production.Location.LocationID " +
            "WHERE ( Production.ProductInventory.Quantity <= 100 ) " +
            "ORDER BY Production.ProductInventory.Quantity, " +
            "Production.Product.Name;";
        }

        #endregion

    }

    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
        public string Location { get; set; }
        public int Quantity { get; set; }

        public override bool Equals(object obj)
        {
            var other = obj as Product;
            if (other == this)
                return true;
            if (ReferenceEquals(this, other))
                return true;
            if (this.ProductId == other.ProductId
                && this.Name.Equals(other.Name)
                && this.Location.Equals(other.Location)
                && this.Quantity.Equals(other.Quantity))
                return true;
            else
                return false;
        }

        public override int GetHashCode()
        {
            return (ProductId.ToString() +
                Name +
                Location +
                Quantity.ToString()).GetHashCode();
        }
    }

}
